package dev.ece.suit.loader;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;

import com.bulletphysics.collision.shapes.CollisionShape;
import com.bulletphysics.collision.shapes.ConvexHullShape;
import com.bulletphysics.util.ObjectArrayList;

import dev.ece.core.cells.model.AbstractModel;
import dev.ece.core.cells.model.ModelCollisionShape;
import dev.ece.core.cells.model.action.ActionManager.Action;
import dev.ece.core.texture.FBOS;
import dev.ece.util.lib.Vector3f;
import dev.ece.util.tools.Camera;

public abstract class AbstractModel3D<T extends Number & Comparable<T>> extends AbstractModel<Frame<T>> implements ModelCollisionShape {

	/**
	 * 动作集合
	 */
	private Map<String, Action<T>> actions = new ConcurrentHashMap<String, Action<T>>();
	
	/**
	 * 包围盒最小点
	 */
	private Vector3f minPoint;
	
	/**
	 * 包围盒最大点
	 */
	private Vector3f maxPoint;
	
	/**
	 * 材质
	 */
	private Material[] materials;
	
	/**
	 * 网格
	 */
	private Mesh[] meshes;
	
	/**
	 * 面
	 */
	private Face[] faces;
	
	/**
	 * 关节信息
	 */
	private Joint<T>[] joints;
	
	/**
	 * 需要更新的关节骨骼信息
	 */
	private Joint<T>[] updateJoints;
	
	/**
	 * 需要渲染的网格序号
	 */
	private int[] defaultIndice;
	/**
	 * 公用模型的时候，相同动作可以不用更新关节信息
	 */
	private T current;
	
	private int frameCount;
	
	private T total;
	
	private ModelShader modelShader;
	
	private ModelDepthShader modelDepthShader;
	
	private ModelLoader<T> loader;
	
	private boolean alpha;
	
	public AbstractModel3D(ModelLoader<T> loader) throws Exception {
		this.loader = loader;
		this.load();
		this.setup();
	}
	
	protected void load() throws IOException {
		try {
			loader.load();
			this.total = loader.total();
			this.frameCount = loader.frameCount();
			this.materials = loader.getMaterials();
			this.faces = loader.getFaces();
			this.updateJoints = loader.getUpdateJoints();
			this.joints = loader.getJoints();
			this.meshes = loader.getMeshes();
			this.modelDepthShader = loader.getModelDepthShader();
			this.modelShader = loader.getModelShader();
			Vector3f[] boundBox = loader.boundBox();
			this.minPoint = boundBox[0];
			this.maxPoint = boundBox[1];
			loader.addAction(actions);
		} finally {
			loader.dispose();
		}
	}
	
	protected void setup() {
		this.defaultIndice = new int[this.meshes.length];
		for(int i=0; i<this.defaultIndice.length; i++) {
			this.defaultIndice[i] = i;
		}
		for(Material material: materials) {
			material.setup();
			this.alpha |= material.getAlpha() < 1;
		}
		for(Mesh mesh:meshes) {
			mesh.setup(faces);
		}
	}
	
	@Override
	public void onInitialize() {
		for(Mesh mesh:meshes) {
			mesh.initialize();
		}
	}

	@Override
	public void onRenderDepthMap(Camera camera, Frame<T> frame) {
		this.modelDepthShader.glUseProgram();
		this.modelDepthShader.glModelViewProjectMatrix();
		//更新关节
		this.updateJoints(frame.getCurrent());
		this.modelDepthShader.glJointsLocalMatrix(joints);
		//渲染深度图
		int[] indice = frame.getIndice();
		for(int i=0; i<indice.length; i++) {
			meshes[indice[i]].renderDepthMap();
		}
	}
	
	@Override
	public void onRender(Camera camera, Frame<T> frame) {
		this.modelShader.glUseProgram();
		this.modelShader.glModelViewProjectMatrix();
		renderModel(frame);
	}
	
	@Override
	public void onRenderAlpha(FBOS defer, Camera camera, Frame<T> frame) {
		this.modelShader.glUseProgram();
		this.modelShader.glModelViewProjectMatrix();
		this.modelShader.glDeferHandler(defer);
		renderModel(frame);
	}
	
	protected void renderModel(Frame<T> frame) {
		//更新关节
		this.updateJoints(frame.getCurrent());
		this.modelShader.glJointsLocalMatrix(joints);
		//渲染
		int[] indice = frame.getIndice();
		for(int i=0; i<indice.length; i++) {
			meshes[indice[i]].render(modelShader, materials);
		}
	}

	@Override
	public CollisionShape createCollisionShape() {
		ObjectArrayList<javax.vecmath.Vector3f> vertice = new ObjectArrayList<javax.vecmath.Vector3f>(8);
		vertice.add(this.getMinPoint().toVector3f());
		vertice.add(new Vector3f(this.getMaxPoint().getX(), this.getMinPoint().getY(), this.getMinPoint().getZ()).toVector3f());
		vertice.add(new Vector3f(this.getMaxPoint().getX(), this.getMaxPoint().getY(), this.getMinPoint().getZ()).toVector3f());
		vertice.add(new Vector3f(this.getMinPoint().getX(), this.getMaxPoint().getY(), this.getMinPoint().getZ()).toVector3f());
		
		vertice.add(new Vector3f(this.getMinPoint().getX(), this.getMinPoint().getY(), this.getMaxPoint().getZ()).toVector3f());
		vertice.add(new Vector3f(this.getMaxPoint().getX(), this.getMinPoint().getY(), this.getMaxPoint().getZ()).toVector3f());
		vertice.add(this.getMaxPoint().toVector3f());
		vertice.add(new Vector3f(this.getMinPoint().getX(), this.getMaxPoint().getY(), this.getMaxPoint().getZ()).toVector3f());
		return new ConvexHullShape(vertice);
	}

	public void updateJoints(T current) {
		if(this.updateJoints != null) {
			if(!current.equals(this.current)) {
				for(Joint<T> joint:this.updateJoints) {
					joint.update(current);
				}
				this.current = current;
			}
		}
	}
	
	@Override
	public void destroy() {
		actions.clear();
		for(Material material: materials) {
			material.destroy();
		}
		for(Mesh mesh:meshes) {
			mesh.destroy();
		}
	}
	
	public boolean isAlpha() {
		return alpha;
	}

	public int[] getDefaultIndice() {
		return defaultIndice;
	}

	public Map<String, Action<T>> getActions() {
		return actions;
	}

	public Joint<T>[] getJoints() {
		return joints;
	}

	public Material[] getMaterials() {
		return materials;
	}

	public Mesh[] getMeshes() {
		return meshes;
	}

	public Face[] getFaces() {
		return faces;
	}

	public T getCurrent() {
		return current;
	}

	public ModelShader getModelShader() {
		return modelShader;
	}

	public ModelDepthShader getModelDepthShader() {
		return modelDepthShader;
	}

	public int indexOfJoint(String name) {
		if(joints != null) {
			for(int i=0; i<joints.length; i++) {
				if(StringUtils.equals(joints[i].getName(), name)) {
					return i;
				}
			}
		}
		return -1;
	}
	
	public int getFrameCount() {
		return frameCount;
	}

	public T getTotal() {
		return total;
	}

	public Vector3f getMinPoint() {
		return minPoint;
	}

	public Vector3f getMaxPoint() {
		return maxPoint;
	}

	/**
	 * 创建帧运算
	 * @return
	 */
	public abstract Frame<T> createFrameInstance();
	
	public abstract T createCurrent(int frame);
	
}
